#include "SymtableV0.h"
#include "Test.h"
#include "Helper.h"
#include <assert.h>
#include <stdio.h>

#ifdef WIN
	//#include "stdafx.h"
#endif 
//WIN

const size_t SymtableV0::HASH_CNT = 1500;

//public methods of SymtabEntry:
SymtabEntry::SymtabEntry(Information info) :
    info(info)
{}

SymtabEntry::~SymtabEntry(){}

SymtabEntry* SymtabEntry::getPrev()
{
    return prev;
}

void SymtabEntry::setPrev(SymtabEntry* prev)
{
    this->prev = prev;
}

Information& SymtabEntry::getInfo()
{
    return info;
}

void SymtabEntry::setInfo(Information info)
{
    this->info = info;
}

//private methods of Symtable:
void SymtableV0::initKeywords()
{
    const char * keyword = NULL;
    TType type;

    keyword = "print";
    putKeyword(keyword, strlen(keyword) + 0);//string-term can be ignored here
    mkToken(type = TOK_PRINT);

    keyword = "read";
    putKeyword(keyword, strlen(keyword) + 0);//string-term can be ignored here
    mkToken(type = TOK_READ);

    keyword = "if";
    putKeyword(keyword, strlen(keyword) + 0);//string-term can be ignored here
    mkToken(type = TOK_IF);

    keyword = "else";
    putKeyword(keyword, strlen(keyword) + 0);//string-term can be ignored here
    mkToken(type = TOK_ELSE);

    keyword = "while";
    putKeyword(keyword, strlen(keyword) + 0);//string-term can be ignored here
    mkToken(type = TOK_WHILE);

    keyword = "int";
    putKeyword(keyword, strlen(keyword) + 0);//string-term can be ignored here
    mkToken(type = TOK_INT);
}

void SymtableV0::putKeyword(const char * const keyword, size_t len)
{
    for(size_t i = 0; i < len; ++i)
    {
        putChar(keyword[i]);
    }
}

int SymtableV0::lookup(Hash hash, Lexem lexem)
{
    //for(SymtabEntry* entry = map[hash]; entry != NULL; entry = entry->getPrev(), entryNrAtHash++)
    SymtabEntry* entry = map[hash];
    for(int triedOffset = entriesPerHash[hash] - 1; entry != NULL && triedOffset >= 0; --triedOffset, entry = entry->getPrev())
    {
        if(strncmp(entry->getInfo().lexem, lexem.getChars(), lexem.getLength()) == 0)
        {//if the lexem is already in the stringtable:
            return triedOffset;
        }
    }
  return -1;
}

//public methods of Symtable:
SymtableV0::SymtableV0() :
    tab(new StringTabV0()),
    map(new SymtabEntry*[HASH_CNT]),
    entriesPerHash(new int[HASH_CNT]),//1-based counting!!!
    lastChar('\0')
{
    assert(sizeof(Key) > sizeof(Hash));//hashes are part of keys

    memset(map, NULL, HASH_CNT * sizeof(SymtabEntry*));//set all pointers to NULL
    
    for(size_t i = 0; i < HASH_CNT; ++i)
    {
        entriesPerHash[i] = 0;
    }

    initKeywords();
}

SymtableV0::~SymtableV0()
{
    delete tab;
    delete[] map;
    delete[] entriesPerHash;
}

Hash SymtableV0::calculateHash(Lexem lexem)
{
    Hash hash = 0;
    if(lexem.getLength() < 6)
    {
        hash  = lexem.getChars()[ 0]  % HASH_CNT;
    } else {
        hash = (lexem.getChars()[0]  +
                1 * lexem.getChars()[1]  +
                3 * lexem.getChars()[2]  +
                13 * lexem.getChars()[3]  +
                23 * lexem.getChars()[4]  +
                53 * lexem.getChars()[5]) % HASH_CNT;
    }
    return hash;
}

void SymtableV0::putChar(char c)
{
    tab->putChar(c);
}

Key SymtableV0::mkToken(TType& type)
{
    Lexem lexem = tab->mkLexem();
    Hash hash = calculateHash(lexem);
    SymtabEntry* entryFirst = map[hash];
    TType realType = TOK_NO_TYPE;

    //search for existing map entries:
    int entryFoundAtOffset = -1;
    if(map[hash] != NULL && (entryFoundAtOffset = lookup(hash, lexem)) != -1)
    {//if a entry is found
        //type = map[hash][entryFoundAtOffset].getInfo().type;
        type = getEntryByOffset(entryFoundAtOffset, hash)->getInfo().type;
        //tab->revertLastLexem();
        Key key = ((long long) entryFoundAtOffset) << (sizeof(int) * 8);//set the upper-part of the {int,int}-variable to the number of entries (values) with the same hash-value
        key    |= hash;                                                 //set the lower-part of the {int,int}-variable to hash
        return key;
    } //else {// create new entry:

    Key key = ((long long) entriesPerHash[hash]) << (sizeof(int) * 8);//set the upper-part of the {int,int}-variable to the number of entries (values) with the same hash-value
    key    |= hash;                               //set the lower-part of the {int,int}-variable to hash
    
    //else: its the first entry or the first entry with that lexem
    SymtabEntry* entryNew = new SymtabEntry(Information(lexem.getChars(), type));//CLGR IMPROVE
    if(entryFirst != NULL)
        entryNew->setPrev(entryFirst);//make first to second entry
    map[hash] = entryNew;

    entriesPerHash[hash]++;

    return key;
}

Information& SymtableV0::lookup(Key key)
{
    int offset = key >> (sizeof(int) * 8);
    Hash hash  = key & (0x00000000FFFFFFFF);
    SymtabEntry* entry = getEntryByOffset(offset, hash);
    return entry->getInfo();
}

SymtabEntry* SymtableV0::getEntryByOffset(int offset, Hash hash)
{
    offset = entriesPerHash[hash] - offset - 1;
    SymtabEntry* entry = map[hash];
    while(offset > 0){ 
        entry = entry->getPrev();
        offset--;
    }
    assert(entry != NULL);
    return entry;
}

SymtabEntry* SymtableV0::getSymtabEntry(Key key)
{
    int offset = key >> (sizeof(int) * 8);
    Hash hash  = key & (0x00000000FFFFFFFF);
    SymtabEntry* entry = getEntryByOffset(offset, hash);
    return entry;
}
